﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Edgar.Entities.Characteristics;
using Edgar.Entities.MasterData;
using System.Windows.Data;

namespace Edgar.Frontend.Wpf.ViewModels
{
    public class CharacterViewModel : INotifyPropertyChanged
    {
        #region Fields

        private readonly Character _character;
        private readonly Rulebook _rulebook;

        public Character GetCharacter()
        {
            return _character;
        }

        #endregion Fields

        #region Personal Characteristics

        public string Name
        {
            get { return _character.Name; }
            set
            {
                _character.Name = value;
                NotifyPropertyChanged("Name");
            }
        }

        public int Age
        {
            get { return _character.Age; }
            set
            {
                _character.Age = value;
                NotifyPropertyChanged("Age");
            }
        }

        public int Height
        {
            get { return _character.Height; }
            set
            {
                _character.Height = value;
                NotifyPropertyChanged("Height");
            }
        }

        public int Weight
        {
            get { return _character.Weight; }
            set
            {
                _character.Weight = value;
                NotifyPropertyChanged("Weight");
            }
        }

        public string SkinColor
        {
            get { return _character.SkinColor; }
            set
            {
                _character.SkinColor = value;
                NotifyPropertyChanged("SkinColor");
            }
        }

        public string HairColor
        {
            get { return _character.HairColor; }
            set
            {
                _character.HairColor = value;
                NotifyPropertyChanged("HairColor");
            }
        }

        public string EyeColor
        {
            get { return _character.EyeColor; }
            set
            {
                _character.EyeColor = value;
                NotifyPropertyChanged("EyeColor");
            }
        }

        public string Gender
        {
            get { return _character.Gender; }
            set
            {
                _character.Gender = value;
                NotifyPropertyChanged("Gender");
            }
        }

        #endregion Personal Characteristics

        #region Discipline and Race

        public string Race
        {
            get { return _character.Race; }
            set
            {
                _character.Race = value;
                NotifyPropertyChanged("Race");
                NotifyPropertyChanged("KarmaDie");
                NotifyPropertyChanged("KarmaCurrent");
                NotifyPropertyChanged("KarmaMaximum");
                NotifyPropertyChanged("KarmaCost");
                NotifyPropertyChanged("RacialSkills");
                NotifyPropertyChanged("ArmorTotalPhysical");
                NotifyPropertyChanged("ArmorTotalPhysicalWithShield");
                NotifyPropertyChangedAllAttributes();

                if (value != null)
                {
                    _character.Height = _rulebook.GetRace(value).AverageHeight;
                    _character.Weight = _rulebook.GetRace(value).AverageWeight;
                    NotifyPropertyChanged("Height");
                    NotifyPropertyChanged("Weight");
                }

                NotifyPropertyChanged("MinimumStrength");
                NotifyPropertyChanged("MaximumStrength");
                NotifyPropertyChanged("MinimumToughness");
                NotifyPropertyChanged("MaximumToughness");
            }
        }

        public List<string> AvailableRaces
        {
            get
            {
                if (Discipline == null) return null;
                var list = new List<string>();
                foreach (Race race in _rulebook.Races)
                {
                    if (!_rulebook.GetDiscipline(Discipline).ExcludedRaceNames.Contains(race.Name))
                        list.Add(race.Name);
                }
                list.Sort();
                return list;
            }
        }

        public string Discipline
        {
            get { return _character.Discipline; }
            set
            {
                _character.Discipline = value;
                _character.Talents = new List<CharacterTalent>();
                NotifyPropertyChanged("Discipline");
                NotifyPropertyChanged("AvailableRaces");
                InitializeTalents(value);
                InitializeSkills();
                NotifyPropertyChanged("Talents");
                NotifyPropertyChanged("Skills");
            }
        }

        public List<string> AvailableDisciplines
        {
            get
            {
                var list = _rulebook.Disciplines.Select(x => x.Name).ToList<string>();
                list.Sort();
                return list;
            }
        }

        public int Circle
        {
            get { return _character.Circle; }
            set
            {
                _character.Circle = value;
                NotifyPropertyChanged("Talents");
            }
        }

        #endregion Discipline and Race

        #region Purchase Points

        public int PurchasePointsTotal { get { return 73; } }
        public int PurchasePointsLeft
        {
            get
            {
                return PurchasePointsTotal -
                    (_rulebook.GetAttributeCost(DexterityValue) +
                    _rulebook.GetAttributeCost(StrengthValue) +
                    _rulebook.GetAttributeCost(ToughnessValue) +
                    _rulebook.GetAttributeCost(PerceptionValue) +
                    _rulebook.GetAttributeCost(WillpowerValue) +
                    _rulebook.GetAttributeCost(CharismaValue));
            }
        }

        public int TalentPointsTotal { get { return 8; } }
        public int TalentPointsLeft
        {
            get
            {
                var count = 0;
                foreach (var talent in _character.Talents)
                {
                    count += talent.Rank;
                }
                return TalentPointsTotal - count;
            }
        }

        public int KnowledgeSkillPointsTotal { get { return 2; } }
        public int KnowledgeSkillPointsLeft
        {
            get
            {
                var count = 0;
                foreach (var skill in _character.Skills.Where(x=>x.Category == "Wissensfertigkeit"))
                {
                    count += skill.Rank;
                }
                return KnowledgeSkillPointsTotal - count;
            }
        }

        public int CraftingSkillPointsTotal { get { return 1; } }
        public int CraftingSkillPointsLeft
        {
            get
            {
                var count = 0;
                foreach (var skill in _character.Skills.Where(x => x.Category == "Kunstfertigkeit"))
                {
                    count += skill.Rank;
                }
                return CraftingSkillPointsTotal - count;
            }
        }

        public int MoneyTotal { get { return 120; } }
        public int MoneyLeft { get { return MoneyTotal; } } // TODO

        #endregion Purchase Points

        #region Attribute Values

        public int DexterityValue
        {
            get { return _character.Dexterity; }
            set
            {
                _character.Dexterity = value;
                NotifyAttributeChanged("Dexterity");
            }
        }

        public int StrengthValue
        {
            get { return _character.Strength; }
            set
            {
                _character.Strength = value;
                NotifyAttributeChanged("Strength");
            }
        }

        public int ToughnessValue
        {
            get { return _character.Toughness; }
            set
            {
                _character.Toughness = value;
                NotifyAttributeChanged("Toughness");
            }
        }

        public int PerceptionValue
        {
            get { return _character.Perception; }
            set
            {
                _character.Perception = value;
                NotifyAttributeChanged("Perception");
            }
        }

        public int WillpowerValue
        {
            get { return _character.Willpower; }
            set
            {
                _character.Willpower = value;
                NotifyAttributeChanged("Willpower");
            }
        }

        public int CharismaValue
        {
            get { return _character.Charisma; }
            set
            {
                _character.Charisma = value;
                NotifyAttributeChanged("Charisma");
            }
        }

        #endregion Attribute Values

        #region Attribute Modificators

        public int DexterityModificator { get { return _rulebook.GetRaceModificator(_character.Race, "Geschicklichkeit"); } }
        public int StrengthModificator { get { return _rulebook.GetRaceModificator(_character.Race, "Stärke"); } }
        public int ToughnessModificator { get { return _rulebook.GetRaceModificator(_character.Race, "Zähigkeit"); } }
        public int PerceptionModificator { get { return _rulebook.GetRaceModificator(_character.Race, "Wahrnehmung"); } }
        public int WillpowerModificator { get { return _rulebook.GetRaceModificator(_character.Race, "Willenskraft"); } }
        public int CharismaModificator { get { return _rulebook.GetRaceModificator(_character.Race, "Charisma"); } }

        #endregion Attribute Modificators

        #region Attribute End Values

        public int DexterityEndValue { get { return DexterityValue + DexterityModificator; } }
        public int StrengthEndValue { get { return StrengthValue + StrengthModificator; } }
        public int ToughnessEndValue { get { return ToughnessValue + ToughnessModificator; } }
        public int PerceptionEndValue { get { return PerceptionValue + PerceptionModificator; } }
        public int WillpowerEndValue { get { return WillpowerValue + WillpowerModificator; } }
        public int CharismaEndValue { get { return CharismaValue + CharismaModificator; } }

        #endregion Attribute End Values

        #region Attribute Steps

        public int DexterityStep { get { return DexterityEndValue / 3 + 2; } }
        public int StrengthStep { get { return StrengthEndValue / 3 + 2; } }
        public int ToughnessStep { get { return ToughnessEndValue / 3 + 2; } }
        public int PerceptionStep { get { return PerceptionEndValue / 3 + 2; } }
        public int WillpowerStep { get { return WillpowerEndValue / 3 + 2; } }
        public int CharismaStep { get { return CharismaEndValue / 3 + 2; } }

        #endregion Attribute Steps

        #region Attribute Dice

        public string DexterityDice { get { return _rulebook.GetDice(DexterityStep); } }
        public string StrengthDice { get { return _rulebook.GetDice(StrengthStep); } }
        public string ToughnessDice { get { return _rulebook.GetDice(ToughnessStep); } }
        public string PerceptionDice { get { return _rulebook.GetDice(PerceptionStep); } }
        public string WillpowerDice { get { return _rulebook.GetDice(WillpowerStep); } }
        public string CharismaDice { get { return _rulebook.GetDice(CharismaStep); } }

        #endregion Attribute Dice

        #region Calculated Characteristics

        public string KarmaDie { get { return _character.Race == null ? null : _rulebook.GetRace(Race).Karma.Die; } }
        public int KarmaCurrent { get { return _character.Race == null ? 0 : _rulebook.GetRace(Race).Karma.Starting; } }
        public int KarmaMaximum { get { return _character.Race == null ? 0 : _rulebook.GetRace(Race).Karma.Maximum; } }
        public int KarmaCost { get { return _character.Race == null ? 0 : _rulebook.GetRace(Race).Karma.Cost; } }
        public List<string> KarmaUsable { get { return new[] { "Stärke", "Erholungsproben" }.ToList<string>(); } } // TODO

        public int SpeedCombat { get { return _rulebook.GetAttributeCharacteristic(DexterityEndValue + _rulebook.GetRaceModificator(_character.Race, "Speed")).SpeedCombat; } }
        public int SpeedNormal { get { return _rulebook.GetAttributeCharacteristic(DexterityEndValue + _rulebook.GetRaceModificator(_character.Race, "Speed")).SpeedNormal; } }

        public int WeightCarrying { get { return _rulebook.GetAttributeCharacteristic(StrengthEndValue).WeightCarrying; } }
        public int WeightLifting { get { return _rulebook.GetAttributeCharacteristic(StrengthEndValue).WeightLifting; } }

        public int LimitDeath { get { return _rulebook.GetAttributeCharacteristic(ToughnessEndValue).LimitDeath; } }
        public int LimitFaint { get { return _rulebook.GetAttributeCharacteristic(ToughnessEndValue).LimitFaint; } }
        public int LimitWound { get { return _rulebook.GetAttributeCharacteristic(ToughnessEndValue).LimitWound + GetBonus(ValueBonus.LimitWound); } }
        public double RegenerationsPerDay { get { return _rulebook.GetAttributeCharacteristic(ToughnessEndValue).RegenerationsPerDay + GetBonus(ValueBonus.Regenerations); } }

        public int MysticArmor { get { return _rulebook.GetAttributeCharacteristic(WillpowerEndValue).MysticArmor; } }

        public double ResistancePhysical { get { return _rulebook.GetAttributeCharacteristic(DexterityEndValue).ResistenceValue + GetBonus(ValueBonus.ResistancePhysical); } }
        public double ResistanceMagical { get { return _rulebook.GetAttributeCharacteristic(PerceptionEndValue).ResistenceValue + GetBonus(ValueBonus.ResistanceMagical); } }
        public double ResistanceSocial { get { return _rulebook.GetAttributeCharacteristic(CharismaEndValue).ResistenceValue + GetBonus(ValueBonus.ResistanceSocial); } }

        public int DeathDurability { get { return _rulebook.GetDeathDurability(Discipline); } }
        public int FaintDurability { get { return _rulebook.GetFaintDurability(Discipline); } }

        public int EndLimitDeath { get { return DeathDurability * RankDurability + LimitDeath; } }
        public int EndLimitFaint { get { return FaintDurability * RankDurability + LimitFaint; } }

        public int ArmorPenalty { get { return 0; } } //TODO
        public int InitiativeStep { get { return DexterityStep - ArmorPenalty + GetBonus(ValueBonus.Initiative); } }
        public string InitiativeDice { get { return _rulebook.GetDice(InitiativeStep); } }

        public int ArmorTotalPhysicalWithShield { get { return ArmorTotalPhysical + 0; } } //TODO
        public int ArmorTotalMysticWithShield { get { return ArmorTotalMystic + 0; } } //TODO
        public int ArmorTotalPhysical { get { return 0 + GetBonus(ValueBonus.ArmorPhysical); } } // TODO
        public int ArmorTotalMystic { get { return MysticArmor + 0; } } // TODO

        #endregion Calculated Characteristics

        #region Racial Skills

        public List<string> RacialSkills { get { return _character.Race == null ? null : _rulebook.GetRace(Race).RaceSkills.Select(x => x.Description).ToList<string>(); } }
        public int MinimumStrength { get { return GetRacialSkillClampValue("min", "Stärke"); } }
        public int MaximumStrength { get { return GetRacialSkillClampValue("max", "Stärke"); } }
        public int MinimumToughness { get { return GetRacialSkillClampValue("min", "Zähigkeit"); } }
        public int MaximumToughness { get { return GetRacialSkillClampValue("max", "Zähigkeit"); } }

        private int GetRacialSkillClampValue(string clampType, string attributeName)
        {
            if (RacialSkills == null) return clampType.ToLower() == "min" ? 2 : 18;
            foreach (string racialSkill in RacialSkills)
            {
                string[] lowerSkillSplit = racialSkill.ToLower().Split(' ');
                if (lowerSkillSplit.Count() < 3) continue;
                int clampValue;
                if (lowerSkillSplit[0].Contains(clampType.ToLower()) &&
                    lowerSkillSplit[1].Contains(attributeName.ToLower()) &&
                    Int32.TryParse(lowerSkillSplit[2].Replace("(", "").Replace(")", ""), out clampValue))
                {
                    switch (clampType.ToLower())
                    {
                        case "min":
                            if (attributeName.ToLower() == "stärke" && StrengthValue < clampValue) StrengthValue = clampValue;
                            if (attributeName.ToLower() == "zähigkeit" && ToughnessValue < clampValue) ToughnessValue = clampValue;
                            break;
                        case "max":
                            if (attributeName.ToLower() == "stärke" && StrengthValue > clampValue) StrengthValue = clampValue;
                            if (attributeName.ToLower() == "zähigkeit" && ToughnessValue > clampValue) ToughnessValue = clampValue;
                            break;
                    }
                    return clampValue;
                }

                //Maximaler Stärkeattributswert 11
                //  Minimaler Stärkeattributswert 15
            }
            return clampType.ToLower() == "min" ? 2 : 18;
        }

        #endregion Racial Skills

        #region Bonuses

        private int GetBonus(ValueBonus valueBonus)
        {
            int value = 0;

            // Racial Skills that give bonuses
            if (RacialSkills != null)
            {
                foreach (string racialSkill in RacialSkills)
                {
                    if (valueBonus == ValueBonus.LimitWound &&
                        racialSkill.ToLower().StartsWith("erhöhte verwundungsschwelle"))
                        value += 3;
                    if (valueBonus == ValueBonus.ArmorPhysical &&
                        racialSkill.ToLower().StartsWith("natürliche panzerung"))
                        value += 3;
                    if (valueBonus == ValueBonus.ResistancePhysical &&
                        racialSkill.ToLower().StartsWith("erhöhte körperliche wiederstandskraft"))
                        value += 2;
                }
            }

            //TODO: add the circle bonuses here!

            return value;
        }

        #endregion Bonuses

        #region Talents

        public int RankDurability
        {
            get
            {
                foreach (var talentViewModel in Talents)
                {
                    if (talentViewModel.Name.ToLower() == "unempfindlichkeit")
                        return talentViewModel.Rank;
                }
                return 0;
            }
        }        

        public List<TalentViewModel> Talents
        {
            get
            {
                List<TalentViewModel> talents = new List<TalentViewModel>();
                foreach (CharacterTalent characterTalent in _character.Talents)
                {
                    int attributeStep = GetAttributeStep(_rulebook.GetTalent(characterTalent.Name).Attribute);
                    var tmv = new TalentViewModel(_rulebook, characterTalent, Circle, attributeStep);
                    talents.Add(tmv);
                    tmv.PropertyChanged += new PropertyChangedEventHandler(tmv_PropertyChanged);
                }

                ICollectionView currentView = CollectionViewSource.GetDefaultView(talents);
                if (currentView.GroupDescriptions.Count < 1) // TODO: woanders hin?
                    currentView.GroupDescriptions.Add(new PropertyGroupDescription("Circle"));

                return talents;
            }
        }

        public void InitializeTalents(string disciplineName)
        {
            _character.Talents.Clear();
            foreach (DisciplineTalent disciplineTalent in _rulebook.GetDisciplineTalents(Discipline))
            {
                _character.Talents.Add(new CharacterTalent() { Name = disciplineTalent.TalentName, Rank = 0, Circle = disciplineTalent.Circle });
            }
        }

        void tmv_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChanged(e.PropertyName);
            if (e.PropertyName == "TalentPointsLeft")
            {
                NotifyPropertyChanged("EndLimitDeath");
                NotifyPropertyChanged("EndLimitFaint");
            }
        }

        #endregion Talents

        #region Skills

        public List<SkillViewModel> Skills
        {
            get
            {
                List<SkillViewModel> skills = new List<SkillViewModel>();
                foreach (CharacterSkill characterSkill in _character.Skills)
                {
                    string attributeName;
                    switch (characterSkill.Category)
                    {
                        case "Wissensfertigkeit": attributeName = "Wahrnehmung"; break;
                        case "Kunstfertigkeit": attributeName = "Charisma"; break;
                        case "Allgemeine Fertigkeit": attributeName = _rulebook.Skills.GeneralSkills.Find(x => x.Name == characterSkill.Name).Attribute; break;
                        default: throw new NotSupportedException("Not supporting category " + characterSkill.Category);
                    }
                    int attributeStep = GetAttributeStep(attributeName);
                    var smv = new SkillViewModel(_rulebook, characterSkill, attributeStep);
                    skills.Add(smv);
                    smv.PropertyChanged += new PropertyChangedEventHandler(smv_PropertyChanged);
                }

                ICollectionView currentView = CollectionViewSource.GetDefaultView(skills);
                if (currentView.GroupDescriptions.Count < 1) // TODO: woanders hin?
                    currentView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));

                return skills;
            }
        }

        void smv_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChanged(e.PropertyName);
        }

        public void AddSkill(string name, string category)
        {
            if (_character.Skills.Find(x=>x.Name == name && x.Category == category) == null)
                _character.Skills.Add(new CharacterSkill { Name = name, Category = category, Rank = 0 });
            NotifyPropertyChanged("Skills");
        }

        public void RemoveSkill(string name, string category)
        {
            _character.Skills.RemoveAll(x => x.Name == name && x.Category == category);
            NotifyPropertyChanged("Skills");
        }

        public void InitializeSkills()
        {
            _character.Skills.Clear();
            
            //_character.Skills.Add(new CharacterSkill() { Name = "Blumen pflücken", Category = "Kunstfertigkeit", Rank = 1 });
            //_character.Skills.Add(new CharacterSkill() { Name = "Gras pflücken", Category = "Kunstfertigkeit", Rank = 0 });
            //_character.Skills.Add(new CharacterSkill() { Name = "Waffenkunde", Category = "Wissensfertigkeit", Rank = 3 });
            //_character.Skills.Add(new CharacterSkill() { Name = "Jagen", Category = "Allgemeine Fertigkeit", Rank = 2 });

            //foreach (DisciplineTalent disciplineTalent in _rulebook.GetDisciplineTalents(Discipline))
            //{
            //    _character.Talents.Add(new CharacterTalent() { Name = disciplineTalent.TalentName, Rank = 0, Circle = disciplineTalent.Circle });
            //}
        }

        #endregion Skills

        private int GetAttributeStep(string attributeName)
        {
            switch (attributeName)
            {
                case "Geschicklichkeit": return DexterityStep;
                case "Stärke": return StrengthStep;
                case "Zähigkeit": return ToughnessStep;
                case "Wahrnehmung": return PerceptionStep;
                case "Willenskraft": return WillpowerStep;
                case "Charisma": return CharismaStep;
                case "": return 0;
                default: throw new NotSupportedException("Attribute not supported: " + attributeName);
            }
        }

        #region Constructors

        public CharacterViewModel(Rulebook rulebook)
        {
            _character = new Character() { Dexterity = 12, Strength = 12, Toughness = 12, Perception = 12, Willpower = 12, Charisma = 12, Circle = 1, Gender = "x" };
            _rulebook = rulebook;
        }

        public CharacterViewModel(Rulebook rulebook, Character character)
        {
            _character = character;
            _rulebook = rulebook;
        }

        #endregion Constructors

        #region Implementing INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyTalentPointsChanged()
        {
            NotifyPropertyChanged("TalentPointsLeft");
        }

        private void NotifyPropertyChanged(string info)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(info));
        }

        private void NotifyPropertyChangedAllAttributes()
        {
            NotifyAttributeChanged("Dexterity");
            NotifyAttributeChanged("Strength");
            NotifyAttributeChanged("Toughness");
            NotifyAttributeChanged("Perception");
            NotifyAttributeChanged("Willpower");
            NotifyAttributeChanged("Charisma");
        }

        private void NotifyAttributeChanged(string attributeName)
        {
            NotifyPropertyChanged(attributeName + "Value");
            NotifyPropertyChanged(attributeName + "Modificator");
            NotifyPropertyChanged(attributeName + "EndValue");
            NotifyPropertyChanged(attributeName + "Step");
            NotifyPropertyChanged(attributeName + "Dice");

            switch (attributeName)
            {
                case "Dexterity":
                    NotifyPropertyChanged("ResistancePhysical");
                    NotifyPropertyChanged("SpeedCombat");
                    NotifyPropertyChanged("SpeedNormal");
                    NotifyPropertyChanged("InitiativeStep");
                    NotifyPropertyChanged("InitiativeDice");
                    break;
                case "Strength":
                    NotifyPropertyChanged("WeightCarrying");
                    NotifyPropertyChanged("WeightLifting");
                    break;
                case "Toughness":
                    NotifyPropertyChanged("LimitDeath");
                    NotifyPropertyChanged("LimitFaint");
                    NotifyPropertyChanged("LimitWound");
                    NotifyPropertyChanged("RegenerationsPerDay");
                    break;
                case "Perception":
                    NotifyPropertyChanged("ResistanceMagical");
                    break;
                case "Willpower":
                    NotifyPropertyChanged("MysticArmor");
                    NotifyPropertyChanged("ArmorTotalMystic");
                    NotifyPropertyChanged("ArmorTotalMysticWithShield");
                    break;
                case "Charisma":
                    NotifyPropertyChanged("ResistanceSocial");
                    break;
                default:
                    throw new NotSupportedException("Unknown attribute: " + attributeName);
            }

            NotifyPropertyChanged("PurchasePointsLeft");
            NotifyPropertyChanged("Talents");
            NotifyPropertyChanged("Skills");
        }

        #endregion Implementing INotifyPropertyChanged
    }
}
